package chemical_formula.interpreter;

import chemical_formula.token.Token;
import chemical_formula.val.*;

public class Parser {
    private final Lexer lexer;
    private Token currentToken;

    public Parser(Lexer lexer) {
        this.lexer = lexer;
        this.currentToken = lexer.getNextToken();
    }

    private void error() throws Exception {
        throw new Exception("Invalid syntax");
    }

    private void eat(Token.TokenType tokenType) throws Exception {
        if (currentToken.type == tokenType) {
            currentToken = lexer.getNextToken();
        }
        else {
            this.error();
        }
    }

    private AST term() throws Exception {
        // term: ELEMENT | LPAREN formula RPAREN
        Token token = currentToken;
        if (token.type == Token.TokenType.ELEMENT) {
            eat(Token.TokenType.ELEMENT);
            return new Element((String) token.value);
        }
        else {
            eat(Token.TokenType.LPAREN);
            AST res = formula();
            eat(Token.TokenType.RPAREN);
            return res;
        }
    }

    private AST term_count() throws Exception {
        // term_count: term (INTEGER)
        AST aTerm = term();
        int count = 1;
        if (currentToken.type == Token.TokenType.INTEGER) {
            Token token = currentToken;
            eat(Token.TokenType.INTEGER);
            count = (int) token.value;
        }
        return new TermNode(aTerm, count);
    }

    private AST formula() throws Exception {
        // formula: term_count (term_count)*
        FormulaNode formulaNode = new FormulaNode();
        AST termNode = term_count();
        formulaNode.add((TermNode) termNode);
        while (currentToken.type == Token.TokenType.ELEMENT || currentToken.type == Token.TokenType.LPAREN) {
            termNode = term_count();
            formulaNode.add((TermNode) termNode);
        }
        return formulaNode;
    }

    private AST formula_count() throws Exception {
        // formula_count: (INTEGER) formula
        int count = 1;
        if (currentToken.type == Token.TokenType.INTEGER) {
            Token token = currentToken;
            eat(Token.TokenType.INTEGER);
            count = (int) token.value;
        }
        AST formulaNode = formula();
        ((FormulaNode) formulaNode).setCount(count);
        return formulaNode;
    }

    private AST expr() throws Exception {
        // expr: formula_count (PLUS formula_count)*
        ExprNode exprNode = new ExprNode();
        AST formulaNode = formula_count();
        exprNode.add((FormulaNode) formulaNode);
        while (currentToken.type == Token.TokenType.PLUS) {
            eat(Token.TokenType.PLUS);
            formulaNode = formula_count();
            exprNode.add((FormulaNode) formulaNode);
        }
        return exprNode;
    }

    private AST equation() throws Exception {
        // equation: expr EQUAL expr
        AST left = expr();
        eat(Token.TokenType.EQUAL);
        AST right = expr();
        return new EqualOp(left, right);
    }

    public AST parse() throws Exception {
        return this.equation();
    }
}
